/*
 * DrawCanvas.java
 *
 * Created on January 12, 2008, 3:06 PM
 */

package picto.draw;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import picto.core.*;
import jpen.*;
import jpen.event.*;

/**
 *
 * @author davedes
 */
public class DrawCanvas extends JPanel {
    
    private ImageState checkeredBackground = null;
    private ImageState topImage = null;
    private LayerGroup layerGroup = null;
    
    private int width;
    private int height;
    
    public static final int CHECKER_SIZE = 10;
    
    private int modifiers = 0;
    private PenManager penMgr;
    private boolean drawingEnabled = true;
    
    /**
     * Creates a new instance of DrawCanvas
     */
    public DrawCanvas() {
        addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent e) {
                modifiers = e.getModifiers();
            }
        });
        
        penMgr = new PenManager(this);
        penMgr.pen.addListener(new DrawInputListener());
    }
    
    public Pen getPen() {
        return penMgr.pen;
    }
        
    public synchronized void addDrawListener(DrawListener drawListener) {
        this.listenerList.add(DrawListener.class, drawListener);
    }
    
    public synchronized void removeDrawListener(DrawListener drawListener) {
        this.listenerList.remove(DrawListener.class, drawListener);
    }
    
    protected void fireDrawEvent(int id, int button, int x, int y, float pressure, int type) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        DrawEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==DrawListener.class) {
                // Lazily create the event:
                if (e == null)
                    e = new DrawEvent(this, id, button, x, y, pressure, type, this.modifiers);
                DrawListener ln = (DrawListener)listeners[i+1];
                switch (id) {
                    case DrawEvent.ID_DRAG:
                        ln.drawDrag(e);
                        break;
                    case DrawEvent.ID_PRESS:
                        ln.drawPress(e);
                        break;
                    case DrawEvent.ID_RELEASE:
                        ln.drawRelease(e);
                        break;
                }
            }
        }
    }
        
    public void installProject(Project project) {
        if (checkeredBackground!=null)
            checkeredBackground.dispose();
        if (topImage!=null)
            topImage.dispose();
        layerGroup = null;
        
        if (project!=null) {
            this.width = project.getWidth();
            this.height = project.getHeight();
            topImage = new ImageState(width, height);
            
            checkeredBackground = new ImageState(width, height, ImageState.DEFAULT_RGB);
            Graphics2D g2d = checkeredBackground.getGraphics();
            DrawCanvas.drawCheckeredBackground(g2d, width, height, CHECKER_SIZE);
            layerGroup = project.getLayerGroup();
        }
        repaint();
    }
    
    public ImageState getTopImage() {
        return topImage;
    }
    
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D)g;
        
        if (checkeredBackground!=null) {
            g2d.drawImage(checkeredBackground.getImage(), 0, 0, null);
        }
        
        if (layerGroup!=null) {
            for (int i=0; i<layerGroup.size(); i++) {
                layerGroup.get(i).paint(g2d);
            }
        }
                
        if (topImage!=null) 
            g2d.drawImage(topImage.getImage(), 0, 0, null);
    }
    
    public static void drawCheckeredBackground(Graphics g, int width, int height, int checkerSize) {
        int rows  = width/checkerSize+1;
        int cols  = height/checkerSize+1;
        boolean checker = false;

        if (rows % 2 != 0)
            rows++;
        if (cols % 2 != 0)
            cols++;

        //paint checkers
        for (int x=0; x<rows; x++) {
            for (int y=0; y<cols; y++) {
                Color color = checker ? Color.lightGray : Color.white;
                g.setColor(color);
                g.fillRect(x*checkerSize, y*checkerSize, checkerSize, checkerSize);
                checker = !checker;
            }
            checker = !checker;
        }
    }
    
    protected class DrawInputListener extends PenAdapter {
        private boolean btnDown = false;
        private int button = DrawEvent.NOBUTTON;
        
        private int toDrawButton(PButton.Type type) {
            switch (type) {
                default: 
                case LEFT: 
                    return DrawEvent.BUTTON_LEFT;
                case RIGHT:
                    return DrawEvent.BUTTON_MIDDLE;
                case CENTER:
                    return DrawEvent.BUTTON_RIGHT;
            }
        }
        
        private int toDrawType(PKind.Type type) {
            switch (type) {
                default: case CURSOR:
                    return DrawEvent.TYPE_CURSOR;
                case STYLUS:
                    return DrawEvent.TYPE_STYLUS;
                case ERASER:
                    return DrawEvent.TYPE_ERASER;
            }
        }
        
        public void penButtonEvent(PButtonEvent ev) {
            if (!isDrawingEnabled())
                return;
            Pen p = getPen();
            btnDown = ev.button.value.booleanValue();
            
            PKind.Type penType = p.getKindType();
            PButton.Type btnType = PButton.Type.values()[ev.button.typeNumber];
            
            int id = btnDown ? DrawEvent.ID_PRESS : DrawEvent.ID_RELEASE;
            button = toDrawButton(btnType);
            int type = toDrawType(penType);
            int x = (int)p.getLevelValue(PLevel.Type.X);
            int y = (int)p.getLevelValue(PLevel.Type.Y);
            float pressure = Math.max(0f, Math.min(1f, p.getLevelValue(PLevel.Type.PRESSURE)));
            if (type == DrawEvent.TYPE_CURSOR)
                pressure = 1f;
            fireDrawEvent(id, button, x, y, pressure, type);
        }

        public void penLevelEvent(PLevelEvent ev) {
            if (!isDrawingEnabled())
                return;
            Pen p = getPen();
            PKind.Type penType = p.getKindType();
            
            if (btnDown) {
                int id = DrawEvent.ID_DRAG;
                int type = toDrawType(penType);
                int x = (int)p.getLevelValue(PLevel.Type.X);
                int y = (int)p.getLevelValue(PLevel.Type.Y);
                float pressure = Math.max(0f, Math.min(1f, p.getLevelValue(PLevel.Type.PRESSURE)));
                if (type == DrawEvent.TYPE_CURSOR)
                    pressure = 1f;
                fireDrawEvent(id, button, x, y, pressure, type);
            }
        }
    }

    public boolean isDrawingEnabled() {
        return drawingEnabled;
    }

    public void setDrawingEnabled(boolean drawingEnabled) {
        this.drawingEnabled = drawingEnabled;
    }
}
